package com.cwh.taskcenter.system;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.alibaba.cola.exception.BizException;
import com.alibaba.fastjson.JSON;
import com.cwh.taskcenter.domain.event.DomainEventPublisher;
import com.cwh.taskcenter.domain.system.event.SmsSendEvent;
import com.cwh.taskcenter.domain.system.model.SmsChannelProperties;
import com.cwh.taskcenter.domain.system.model.SmsReceiveStatusEnum;
import com.cwh.taskcenter.domain.system.model.SmsSendStatusEnum;
import com.cwh.taskcenter.domain.system.model.SmsTemplate;
import com.cwh.taskcenter.domain.system.model.StatusEnum;
import com.cwh.taskcenter.domain.system.domainservice.SmsSendDomainService;
import com.cwh.taskcenter.external.sms.SmsClient;
import com.cwh.taskcenter.external.sms.SmsClientFactory;
import com.cwh.taskcenter.external.sms.dto.SmsSendRespDTO;
import com.cwh.taskcenter.system.converter.SmsTemplateConverter;
import com.cwh.taskcenter.system.mapper.SmsChannelMapper;
import com.cwh.taskcenter.system.mapper.SmsLogMapper;
import com.cwh.taskcenter.system.mapper.SmsTemplateMapper;
import com.cwh.taskcenter.system.model.SmsChannelDO;
import com.cwh.taskcenter.system.model.SmsLogDO;
import com.cwh.taskcenter.system.model.SmsTemplateDO;
import com.cwh.taskcenter.util.BeanUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author cwh
 */
@Slf4j
@Service
public class SmsSendDomainServiceImpl implements SmsSendDomainService {

    @Autowired
    private SmsTemplateMapper smsTemplateMapper;

    @Autowired
    private SmsChannelMapper smsChannelMapper;

    @Autowired
    private SmsLogMapper smsLogMapper;

    @Autowired
    private DomainEventPublisher domainEventPublisher;

    @Resource
    private SmsClientFactory smsClientFactory;

    @Override
    public Long sendSingleSms(String mobile, Long userId, Integer userType, String templateCode, Map<String, Object> templateParams) {
        // 校验短信模板是否合法
        SmsTemplate template = validateSmsTemplate(templateCode);
        // 校验短信渠道是否合法
        SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId());

        // 校验手机号码是否存在
        mobile = validateMobile(mobile);
        // 构建有序的模板参数。为什么放在这个位置，是提前保证模板参数的正确性，而不是到了插入发送日志
        List<Map<String, Object>> newTemplateParams = buildTemplateParams(template, templateParams);

        // 创建发送日志。如果模板被禁用，则不发送短信，只记录日志
        Boolean isSend = StatusEnum.ENABLE.getStatus().equals(template.getStatus())
                && StatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus());
        String content = template.formatContent(templateParams);

        // 持久化
        Long logId = createSmsLog(mobile, userId, userType, isSend, template, content, templateParams);

        if (isSend) {
            // 发送事件（解耦具体实现）
            SmsSendEvent smsSendEvent = SmsSendEvent.builder().logId(logId).mobile(mobile)
                    .channelId(template.getChannelId()).apiTemplateId(template.getApiTemplateId())
                    .templateParams(newTemplateParams).build();
            domainEventPublisher.publish(smsSendEvent);
        }
        return logId;
    }

    @Override
    public Long createSmsLog(String mobile, Long userId, Integer userType, Boolean isSend, SmsTemplate template, String templateContent, Map<String, Object> templateParams) {
        SmsLogDO.SmsLogDOBuilder logBuilder = SmsLogDO.builder();
        // 根据是否要发送，设置状态
        logBuilder.sendStatus(Objects.equals(isSend, true) ? SmsSendStatusEnum.INIT.getStatus()
                : SmsSendStatusEnum.IGNORE.getStatus());
        // 设置手机相关字段
        logBuilder.mobile(mobile).userId(userId).userType(userType);
        // 设置模板相关字段
        logBuilder.templateId(template.getId()).templateCode(template.getCode()).templateType(template.getType());
        logBuilder.templateContent(templateContent).apiTemplateId(template.getApiTemplateId())
                .templateParams(JSON.toJSONString(templateParams));
        // 设置渠道相关字段
        logBuilder.channelId(template.getChannelId()).channelCode(template.getChannelCode());
        // 设置接收相关字段
        logBuilder.receiveStatus(SmsReceiveStatusEnum.INIT.getStatus());

        // 插入数据库
        SmsLogDO logDO = logBuilder.build();
        smsLogMapper.create(logDO);
        return logDO.getId();
    }

    @Override
    public void doSendSms(SmsSendEvent message) {
        // 获得渠道对应的 SmsClient 客户端
        SmsClient smsClient = getSmsClient(message.getChannelId());
        Assert.notNull(smsClient, "短信客户端({}) 不存在", message.getChannelId());
        // 发送短信
        try {
            SmsSendRespDTO sendResponse = smsClient.sendSms(message.getLogId(), message.getMobile(),
                    message.getApiTemplateId(), message.getTemplateParams());
            updateSmsSendResult(message.getLogId(), sendResponse.getSuccess(),
                    sendResponse.getApiCode(), sendResponse.getApiMsg(),
                    sendResponse.getApiRequestId(), sendResponse.getSerialNo());
        } catch (Throwable ex) {
            log.error("[doSendSms][发送短信异常，日志编号({})]", message.getLogId(), ex);
           updateSmsSendResult(message.getLogId(), false,
                    "EXCEPTION", ExceptionUtil.getRootCauseMessage(ex), null, null);
        }
    }


    public SmsTemplate validateSmsTemplate(String templateCode) {
        // 获得短信模板。考虑到效率，从缓存中获取
        SmsTemplateDO template = smsTemplateMapper.selectByCode(templateCode);
        // 短信模板不存在
        if (template == null) {
            throw new BizException("SMS_SEND_TEMPLATE_NOT_EXISTS");
        }
        return SmsTemplateConverter.convert2Domain( template);
    }

    public String validateMobile(String mobile) {
        if (StrUtil.isEmpty(mobile)) {
            throw new BizException("SMS_SEND_MOBILE_NOT_EXISTS");
        }
        return mobile;
    }

    /**
     * 将参数模板，处理成有序的 KeyValue 数组
     * <p>
     * 原因是，部分短信平台并不是使用 key 作为参数，而是数组下标，例如说 <a href="https://cloud.tencent.com/document/product/382/39023">腾讯云</a>
     *
     * @param template       短信模板
     * @param templateParams 原始参数
     * @return 处理后的参数
     */
    List<Map<String, Object>> buildTemplateParams(SmsTemplate template, Map<String, Object> templateParams) {
        return template.getParams().stream().map(key -> {
            Object value = templateParams.get(key);
            if (value == null) {
                throw new BizException("SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS", key);
            }
            Map<String, Object> map = new HashMap<>();
            map.put(key, value);
            return map;
        }).collect(Collectors.toList());
    }

    SmsChannelDO validateSmsChannel(Long channelId) {
        // 获得短信模板。考虑到效率，从缓存中获取
        SmsChannelDO channelDO = smsChannelMapper.findById(channelId);
        // 短信模板不存在
        if (channelDO == null) {
            throw new BizException("SMS_CHANNEL_NOT_EXISTS");
        }
        return channelDO;
    }


    public SmsClient getSmsClient(Long id) {
        SmsChannelDO channel = smsChannelMapper.findById(id);
        SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class);
        return smsClientFactory.createOrUpdateSmsClient(properties);
    }

    public void updateSmsSendResult(Long id, Boolean success,
                                    String apiSendCode, String apiSendMsg,
                                    String apiRequestId, String apiSerialNo) {
        SmsSendStatusEnum sendStatus = success ? SmsSendStatusEnum.SUCCESS : SmsSendStatusEnum.FAILURE;
        smsLogMapper.update(SmsLogDO.builder().id(id)
                .sendStatus(sendStatus.getStatus()).sendTime(LocalDateTime.now())
                .apiSendCode(apiSendCode).apiSendMsg(apiSendMsg)
                .apiRequestId(apiRequestId).apiSerialNo(apiSerialNo).build());
    }

}
